package top.ljlgithub.api.shiro;

import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.ljlgithub.api.jwt.JwtFilter;
import top.ljlgithub.api.jwt.JwtUtils;
import top.ljlgithub.api.shiro.cache.RedisCacheManager;
import top.ljlgithub.api.shiro.realm.JwtRealm;
import top.ljlgithub.api.shiro.realm.ShiroRealm;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName
 * @Description TODO
 * @Author L1470
 * @Date 2021/4/23 21:18
 * @Version 1.0
 **/
@Configuration
public class ShiroConfig {
    //1.让Spring自动管理Shiro-Bean的生命周期
    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    //2.为shiro-bean开启注解支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    //    开启shiro注解
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator app = new DefaultAdvisorAutoProxyCreator();
        app.setProxyTargetClass(true);
        return app;

    }

    /**
     * 不启用 JwtFilter Bean，防止 Spring 将 JwtFilter 注册为全局过滤器
     * 全局过滤器会对所有请求进行拦截，而本例中只需要拦截除 /login 和 /logout 外的请求
     * 另一种简单做法是：直接去掉 jwtFilter()上的 @Bean 注解
     */
//    FilterRegistrationBean可以用来实现过滤器，注册自定义过滤器（通过这个可以设置过滤器之间的优先级）
    @Bean
    public FilterRegistrationBean<Filter> registration(JwtFilter filter) {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<Filter>(filter);
        registration.setEnabled(false);// 是否启用，如果值为false，则不启用该 Filter
        return registration;
    }

//    将jwtFilter注册到容器
    @Bean
    public JwtFilter jwtFilter() {
        return new JwtFilter();
    }

    /**
     * 配置访问资源需要的权限
     */
    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/authorized");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        // 添加 jwt 专用过滤器，拦截除 /login 和 /logout 外的请求
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwtFilter", jwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/login", "anon"); // 可匿名访问
        filterChainDefinitionMap.put("/uploads/**", "anon"); // 可匿名访问
        filterChainDefinitionMap.put("/tmp_uploads/**", "anon"); // 可匿名访问
        //filterChainDefinitionMap.put("/logout", "anon"); // 退出登录
        filterChainDefinitionMap.put("/**", "jwtFilter,authc"); // 所有请求需要jwt过滤器认证以及身份认证
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 配置 ModularRealmAuthenticator
     */
//    ModularRealmAuthenticator类主要是用来实现项目中出现多realm的情况下，根据认证策略来对各个realm进行认证
//    如果应用程序中仅配置了一个Realm，Realm将被直接调用而无需再配置认证策略。

    @Bean
    public ModularRealmAuthenticator authenticator() {
        ModularRealmAuthenticator authenticator = new MultiRealmAuthenticator();
        // 设置多 Realm的认证策略，默认 AtLeastOneSuccessfulStrategy
//        FirstSuccessfulStrategy:只要有一个Realm验证成功即可，只返回第一个Realm身份验证成功的认证信息，其他的忽略；(注：这里"第一个"指的是认证成功得那一个realm)
//        AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可，和FirstSuccessfulSTRATEGY不同，将返回所有Realm身份验证成功的认证信息；
//        AllSuccessfulStrategy:所有Realm验证成功过才算成功，且返回所有Realm身份验证的认证信息，如果有一个失败就失败。
        AuthenticationStrategy strategy = new FirstSuccessfulStrategy();
        authenticator.setAuthenticationStrategy(strategy);
        return authenticator;
    }

    /**
     * 禁用session, 不保存用户登录状态。默认是开启的
     * 为了保证每次请求都重新认证，每次都会authentication，
     */
    @Bean
    protected SessionStorageEvaluator sessionStorageEvaluator() {
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        return sessionStorageEvaluator;
    }

    /**
     * JwtRealm 配置，需实现 Realm 接口
     */
    @Bean
    JwtRealm jwtRealm() {
        JwtRealm jwtRealm = new JwtRealm();
//        创建jwt专用的凭证校验匹配器
        CredentialsMatcher credentialsMatcher = new JwtCredentialsMatcher();
//        将凭证校验匹配器交给jwtRealm替换掉默认的
        jwtRealm.setCredentialsMatcher(credentialsMatcher);
        //开启缓存管理
        jwtRealm.setCacheManager(new RedisCacheManager());
        jwtRealm.setCachingEnabled(true);//开启全局缓存
        jwtRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
        jwtRealm.setAuthorizationCacheName("authorizationCache");//设置授权的名字
        return jwtRealm;
    }

    /**
     * ShiroRealm 配置，需实现 Realm 接口
     */
    @Bean
    ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
//        创建hash凭证校验匹配器并设置加密算法为md5
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("md5");
        // 凭证匹配器设置hash散列次数
        credentialsMatcher.setHashIterations(JwtUtils.hashIterations);
        // shiroRealm使用自己的hash凭证匹配器
        shiroRealm.setCredentialsMatcher(credentialsMatcher);
        return shiroRealm;
    }

    /**
     * 配置 SecurityManager安全管理器
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 1.Authenticator，设置多Realm策略
        securityManager.setAuthenticator(authenticator());

        // 2.添加Realm
        List<Realm> realms = new ArrayList<Realm>(16);
        realms.add(jwtRealm());
        realms.add(shiroRealm());
        securityManager.setRealms(realms);

        // 3.关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
//        设置禁用了功能的session替换自带的默认启动的session
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }
}
